#include "Scheduler.h"

// ------------------
// First In First Out
// ------------------
void FIFO::AddProcess(Process * p) {
	if (p != NULL) {
		p->timeAllotted = 0;
		processQueue[p->issueTime].push(p);
	}
}

Process * FIFO::GetNextProcess() {
	Process * p;
	map <unsigned long, queue <Process *> >::iterator iter;

	// Check if there are any available processes
	if (processQueue.empty()) {
		return NULL;
	}

	// Get the queue with the earliest issue time
	iter = processQueue.begin();

	// Get the task at the front of the queue
	p = iter->second.front();
	iter->second.pop();

	// If the queue is now empty then erase it
	if (iter->second.empty()) {
		processQueue.erase(iter);
	}

	return p;
}

bool FIFO::ShouldPreempt(Process * p) {
	return false;	
}



// ------------------
// Shortest Job First
// ------------------
void SJF::AddProcess(Process * p) {
	if (p == NULL) {
		return;
	}

	p->timeAllotted = 0;
	p->timeUsed = 0;
	processQueue[(p->executionTime - p->timeTotalProductive)].push(p);
}

Process * SJF::GetNextProcess() {
	map < unsigned long , queue <Process *> >::iterator iter;
	Process * p;

	// Check if there are any available processes
	if (processQueue.empty()) {
		return NULL;
	}

	// Get the queue with the shortest execution time
	iter = processQueue.begin();

	// Get the task at the front of the queue
	p = iter->second.front();
	iter->second.pop();

	// If the queue is now empty then erase it
	if (iter->second.empty()) {
		processQueue.erase(iter);
	}

	return p;
}

bool SJF::ShouldPreempt(Process * p) {
	map < unsigned long , queue <Process *> >::iterator iter;

	// Check if there are any available processes
	if (processQueue.empty()) {
		return false;
	}

	// Check if there is a shorter process
	iter = processQueue.begin();
	if (iter->first < (p->executionTime - p->timeTotalProductive))
		return true;

	return false;
}



// -----------
// Round Robin
// -----------
RR::RR(unsigned long timeSliceSize) {
	this->timeSliceSize = timeSliceSize;
}

void RR::AddProcess(Process * p) {
	if (p != NULL) {
		p->timeAllotted = timeSliceSize;
		p->timeUsed = 0;
		processQueue.push(p);
	}
}

Process * RR::GetNextProcess() {
	Process * p;

	// Check if there are any available processes
	if (processQueue.empty()) {
		return NULL;
	}

	// Get the task at the front of the queue
	p = processQueue.front();
	processQueue.pop();

	return p;
}

bool RR::ShouldPreempt(Process * p) {
	return false;
}



// --------------
// Priority Queue
// --------------
void PQ::AddProcess(Process * p) {
	if (p == NULL) {
		return;	
	}

	p->timeAllotted = 0;
	p->timeUsed = 0;

	processQueue[p->priority].push(p);
}

Process * PQ::GetNextProcess() {
	Process * p;
	map < unsigned long , queue <Process *> >::iterator it;

	// Check if there are any available processes
	if (processQueue.empty()) {
		return NULL;
	}

	// Get the highest priority queue
	it = processQueue.end();
	it--;

	// Get the task at the front of the queue
	p = it->second.front();
	it->second.pop();

	// If the queue is now empty then erase it
	if(it->second.empty()) {
		processQueue.erase(it);
	}

	return p;
}

bool PQ::ShouldPreempt(Process * p) {
	map < unsigned long , queue <Process *> >::iterator it;

	// Check if there are any available processes
	if (processQueue.empty()) {
		return false;
	}

	// Check if there is a higher priority process
	it = processQueue.end();
	it--;
	if (it->first > p->priority) {
		return true;
	}

	return false;
}



// -----------------------
// Earliest Deadline First
// -----------------------
void EDF::AddProcess(Process * p) {
	if (p == NULL) {
		return;	
	}

	p->timeAllotted = 0;
	p->timeUsed = 0;

	processQueue[p->deadline].push(p);
}

Process * EDF::GetNextProcess() {
	Process * p;
	map < unsigned long , queue <Process *> >::iterator it;

	// Check if there are any available processes
	if (processQueue.empty()) {
		return NULL;
	}

	// Get the lowest deadline queue
	it = processQueue.begin();

	// Get the task at the front of the queue
	p = it->second.front();
	it->second.pop();

	// If the queue is now empty then erase it
	if(it->second.empty()) {
		processQueue.erase(it);
	}

	return p;
}

bool EDF::ShouldPreempt(Process * p) {
	map < unsigned long , queue <Process *> >::iterator it;
	
	// Check if there are any available processes
	if (processQueue.empty()) {
		return false;
	}

	// Check if there is a process with an earlier deadline
	it = processQueue.begin();
	if (it->first < p->deadline) {
		return true;
	}
	
	return false;
}



// --------------------------
// Multi-Level Feedback Queue
// --------------------------
MLFQ::MLFQ(vector <unsigned long> timeSliceSizes) {
	this->timeSliceSizes = timeSliceSizes;
	processQueues.resize(timeSliceSizes.size());
}

void MLFQ::AddProcess(Process * p) {
	int i;

	if (p == NULL) {
		return;
	}

	// If new process then add to highest level
	if (p->timeAllotted == 0) {
		p->timeAllotted = timeSliceSizes[0];
		p->timeUsed = 0;
		processQueues[0].push(p);
		return;
	}

	// Find the queue that the process came from
	for (i = 0; i < processQueues.size(); ++i) {
		if (p->timeAllotted == timeSliceSizes[i]) {
			break;
		}
	}

	// Determine whether process should have priority upgraded or downgraded
	if (p->timeUsed >= p->timeAllotted) {
		if (i < (processQueues.size() - 1)) {
			++i;
		}
	} else if ((i > 0) && (p->timeUsed < timeSliceSizes[i-1])) {
		--i;
	}
	p->timeAllotted = timeSliceSizes[i];
	p->timeUsed = 0;
	processQueues[i].push(p);
}

Process * MLFQ::GetNextProcess() {
	int i;
	Process * p;

	// Get the first task in the highest priority queue
	p = NULL;
	for (i = 0; i < processQueues.size(); ++i) {
		if (!processQueues[i].empty()) {
			p = processQueues[i].front();
			processQueues[i].pop();
			break;
		}
	}

	return p;
}

bool MLFQ::ShouldPreempt(Process * p) {
	int i, queueNumber;
	
	// Find the queue that the process came from
	for (queueNumber = 0; queueNumber < processQueues.size(); ++queueNumber) {
		if (p->timeAllotted == timeSliceSizes[queueNumber]) {
			break;
		}
	}

	// Check if any higher priority queue has an available process
	for (i = 0; i < queueNumber; ++i) {
		if (!processQueues[i].empty()) {
			return true;
		}
	}
	
	return false;
}
